// --------------------------------------------------------------------------------------------------------------------
// <copyright file="VideoStreamControl.xaml.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Visualization
{
    using System;
    using System.IO;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;

    /// <summary>
    /// Interaction logic for VideoStreamControl.xaml
    /// </summary>
    public partial class VideoStreamControl : UserControl
    {
        /// <summary>
        /// Identifies the Frame dependency property. This is needed to enable data binding on this control.
        /// </summary>
        public static readonly DependencyProperty FrameProperty = DependencyProperty.RegisterAttached(
            "Frame", 
            typeof(VideoFrame), 
            typeof(VideoStreamControl), 
            new FrameworkPropertyMetadata(new VideoFrame(), null, new CoerceValueCallback(CoerceValue)));

        /// <summary>
        /// The bitmap that holds the frame being displayed
        /// </summary>
        private WriteableBitmap bitmap;

        /// <summary>
        /// Initializes a new instance of the VideoStreamControl class.
        /// </summary>
        public VideoStreamControl()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Gets or sets the frame to render.
        /// </summary>
        public VideoFrame Frame
        {
            get
            {
                return (VideoFrame)this.GetValue(FrameProperty);
            }

            set
            {
                this.SetValue(FrameProperty, value);
            }
        }

        /// <summary>
        /// Called in response to an update to a databound data source.
        /// This method is expected to convert the source data to the target property type,
        /// but we use it to simply update the image with a new frame.
        /// </summary>
        /// <param name="element">The parent object being notified</param>
        /// <param name="value">The updated value of the source object</param>
        /// <returns>The value, unchanged.</returns>
        private static object CoerceValue(DependencyObject element, object value)
        {
            if (element is VideoStreamControl)
            {
                VideoFrame imageDescriptor = (VideoFrame)value;
                ((VideoStreamControl)element).UpdateFrame(imageDescriptor);
            }

            return value;
        }

        /// <summary>
        /// Updates the image with the new frame.
        /// </summary>
        /// <param name="frame">Updates the view with the new frame</param>
        private void UpdateFrame(VideoFrame frame)
        {
            if (frame.Data == null)
            {
                return;
            }

            // make sure we have a bitmap of the right size to write to
            if (this.bitmap == null || this.bitmap.PixelHeight != frame.Size.Height
                || this.bitmap.PixelWidth != frame.Size.Width)
            {
                PixelFormat pixelFormat = this.GetPixelFormatFromSimplePixelFormat(frame.Format);
                this.bitmap = new WriteableBitmap(frame.Size.Width, frame.Size.Height, 96, 96, pixelFormat, null);
                this.image.Source = this.bitmap;
            }

            // this is a struct, so no point in caching it
            Int32Rect rect = new Int32Rect(0, 0, frame.Size.Width, frame.Size.Height);

            // write the new frame to the backbuffer
            this.bitmap.WritePixels(rect, frame.Data, frame.Stride, 0);
        }

        /// <summary>
        /// Gets the pixel format from the simple pixel format
        /// </summary>
        /// <param name="simplePixelFormat">Simple pixel format</param>
        /// <returns>Pixel format corresponding to the simple pixel format</returns>
        private PixelFormat GetPixelFormatFromSimplePixelFormat(SimplePixelFormat simplePixelFormat)
        {
            switch (simplePixelFormat)
            {
                case SimplePixelFormat.Bgra32:
                    return PixelFormats.Bgra32;
                case SimplePixelFormat.Rgb24:
                    return PixelFormats.Rgb24;
                case SimplePixelFormat.Gray16:
                    return PixelFormats.Gray16;
                case SimplePixelFormat.Gray8:
                    return PixelFormats.Gray8;
                default:
                    throw new System.ArgumentOutOfRangeException("Unsupported simple pixel format");
            }
        }

        /// <summary>
        /// Called to save the bitmap to a file.
        /// </summary>
        /// <param name="sender">The sender object.</param>
        /// <param name="e">The event args.</param>
        private void OnSaveToFile(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            PngBitmapEncoder encoder = new PngBitmapEncoder();
            using (FileStream stream = new FileStream(DateTime.Now.Ticks + ".png", FileMode.Create))
            {
                encoder.Frames.Add(BitmapFrame.Create(this.bitmap));
                encoder.Save(stream);
            }
        }
    }
}
